Animation: When and Why?
Introducing the Tools
Hands-On Tutorial
June 2019
Animation: When and Why?
Introducing the Tools
Hands-On Tutorial
What are some reasons to animate a plot?
Also: If you need to see all the data points at the same time DON’T use animation
library(ggplot2) # make plots library(gganimate) # animate the plots library(gifski) # render gifs library(transformr) # insert smooth transformations
Using data on displaced persons living in South Africa, show patterns and trends in refugee movement over twenty-plus years.
Task: show complex data across many levels of a facet
Problems: Facet grid and facet wrap rapidly get out of their depth
Follow along with the notebook: https://github.com/skirmer/animating_dataviz/blob/master/ioslides_deck.Rmd
Data Source: UNHCR via data.world: UNHCR’s populations of concern residing in South Africa
Grab dataset of displaced persons living in South Africa
| X_date_year | X_country_residence | X_country_origin | X_population_type | X_affected |
|---|---|---|---|---|
| 1993 | South Africa | Mozambique | Refugees (incl. refugee-like situations) | 250000 |
| 1994 | South Africa | Angola | Refugees (incl. refugee-like situations) | 581 |
| 1994 | South Africa | Dem. Rep. of the Congo | Refugees (incl. refugee-like situations) | 808 |
| 1994 | South Africa | Ethiopia | Refugees (incl. refugee-like situations) | 66 |
Select the top 10 countries of origin for each year, apply some filters.
library(magrittr) library(dplyr) plotDT <- dtset %>% filter(X_country_origin != "Various/Unknown") %>% filter(X_population_type == "Refugees (incl. refugee-like situations)") %>% filter(X_date_year > 1995) %>% group_by(X_date_year) %>% mutate(rank = rank(-X_affected, ties.method = "first") * 1) %>% ungroup() %>% filter(rank <= 10) %>% data.frame()
| X_date_year | X_country_residence | X_country_origin | X_population_type | X_affected | rank | state_time |
|---|---|---|---|---|---|---|
| 1996 | South Africa | Angola | Refugees (incl. refugee-like situations) | 3876 | 1 | 1 |
| 1996 | South Africa | Bangladesh | Refugees (incl. refugee-like situations) | 452 | 9 | 1 |
| 1996 | South Africa | China | Refugees (incl. refugee-like situations) | 469 | 8 | 1 |
| 1996 | South Africa | Dem. Rep. of the Congo | Refugees (incl. refugee-like situations) | 2505 | 3 | 1 |
Group by country, bar height is affected persons
baseplot1 <- ggplot(plotDT,
aes(X_date_year,
group = X_country_origin,
fill = as.factor(X_country_origin),
color = as.factor(X_country_origin)))+
theme_bw()+
theme(legend.position = "bottom")+
geom_bar(aes(y = X_affected), stat = "identity", position = "dodge")
Ew. This is not effective.
Let’s make this!
baseplot2 <- baseplot1 +
coord_flip(clip = "off", expand = FALSE, ylim = c(0, 50000))
Can’t use the same stub now.
baseplot3 <- ggplot(plotDT,
aes(x=rank,
group = X_country_origin,
fill = as.factor(X_country_origin),
color = as.factor(X_country_origin)))+
theme_bw()+
theme(legend.position = "bottom")+
geom_text(aes(y = 0, label = paste(X_country_origin, " ")), vjust = 0.2, hjust = 1) +
coord_flip(clip = "off", expand = FALSE, ylim = c(0, 50000)) +
geom_bar(aes(y = X_affected), stat = "identity", position = "identity")
Left side: grouped bar, sorted by year
Right side: not grouped bar, sorted by rank
If your stub has a theme() segment, applying a new one will overrule it.
baseplot3 <- baseplot3 +
theme(legend.position = "none",
axis.ticks.y = element_blank(), # These relate to the axes post-flip
axis.text.y = element_blank(), # These relate to the axes post-flip
axis.title.y = element_blank(),
plot.margin = margin(1,1,1,5, "cm"))
baseplot3 <- baseplot3 + scale_y_continuous(labels = scales::comma) + scale_x_reverse()
Now we have the different “frames” all layered on top of each other.
We need to present the data on an annual basis in a way that tells us:
No.
Definitely not.
Literally add one more line of code to your ggplot object.
animp <- baseplot3 + transition_states(X_date_year)
It’s nice, but we can do better
Solving Problem 1 and 2: Added a descriptive title/label that indicate the year of the frame
animp <- baseplot3 +
geom_text(aes(y = X_affected,
label = as.character(X_affected)),
color = "black", vjust = 0.2, hjust = -.5)+
labs(title = "Refugees Residing in South Africa by Origin, {closest_state}"
, y="Affected Persons")+
transition_states(X_date_year)
Solving Problem 3: how do we want the animation elements to move?
Option: shrink and grow on exit and enter
animp <- baseplot3 +
geom_text(aes(y = X_affected,
label = as.character(X_affected)),
color = "black", vjust = 0.2, hjust = -.5)+
labs(title = "Refugees Residing in South Africa by Origin, {closest_state}"
, y="Affected Persons")+
transition_states(X_date_year)+
enter_grow() +
exit_shrink()
It’s interesting, but probably not serving the project objectives
Solving Problem 3: how do we want the animation elements to move?
Option: Ease between positions (moving on page, not exiting or entering)
animp <- baseplot3 +
geom_text(aes(y = X_affected,
label = as.character(X_affected)),
color = "black", vjust = 0.2, hjust = -.5)+
labs(title = "Refugees Residing in South Africa by Origin, {closest_state}"
, y="Affected Persons")+
transition_states(X_date_year)+
ease_aes('quartic-in-out')
Hopefully, the movement feels less abrasive to the eye now